package com.ophiux.cs.infrastructure.compoment.basedata.mybatis.interceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.session.RowBounds;
import org.hibernate.dialect.Dialect;

/**
 * mysql 物理分页处理
 */
@Intercepts({@org.apache.ibatis.plugin.Signature(type=org.apache.ibatis.executor.Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, org.apache.ibatis.session.ResultHandler.class})})
public class MysqlOffsetLimitInterceptor implements Interceptor
{

	private int MAPPED_STATEMENT_INDEX;
    private int PARAMETER_INDEX;
    private int ROWBOUNDS_INDEX;
    private Dialect dialect;
    
    public MysqlOffsetLimitInterceptor()
    {
    	   this.MAPPED_STATEMENT_INDEX = 0;
	      this.PARAMETER_INDEX = 1;
	      this.ROWBOUNDS_INDEX = 2; 
	}
    
	public Object intercept(Invocation invocation) throws Throwable
	{
		   processIntercept(invocation.getArgs());
	      return invocation.proceed();
	}

	public Object plugin(Object target) {
		// TODO Auto-generated method stub
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		
	}
	 private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource)
	    {
	      MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

	      builder.resource(ms.getResource());
	      builder.fetchSize(ms.getFetchSize());
	      builder.statementType(ms.getStatementType());
	      builder.keyGenerator(ms.getKeyGenerator());
//xgy	    
//	      System.out.println("xgyxgy \n\n  "+ms.getKeyProperties());
	      builder.keyProperty(ms.getKeyProperties()==null?null:ms.getKeyProperties()[0]);
	      
	      builder.timeout(ms.getTimeout());
	      builder.parameterMap(ms.getParameterMap());
	      builder.resultMaps(ms.getResultMaps());
	      builder.resultSetType(ms.getResultSetType());
	      builder.cache(ms.getCache());
	      builder.flushCacheRequired(ms.isFlushCacheRequired());
	      builder.useCache(ms.isUseCache());
	      return builder.build();
	    }
	private String addParameter(List<ParameterMapping> parameterMapping, MappedStatement ms, Object parameter, int offset, int limit, String sql, boolean symbolable)
    {
		  if ((parameter instanceof Map)) {
	          Map map = (Map)parameter;
	          if ((symbolable) && (offset > 0)) {
	            map.put("offset", Integer.valueOf(offset));
	            map.put("limit", Integer.valueOf(limit));
//	            map.put("limit", Integer.valueOf(offset + limit));
	          } else {
	            map.put("limit", Integer.valueOf(limit));
	          }
	        }
	        parameterMapping.add(new ParameterMapping.Builder(ms.getConfiguration(), "limit", Integer.class).build());

	        if ((symbolable) && (offset > 0)) {
	          parameterMapping.add(new ParameterMapping.Builder(ms.getConfiguration(), "offset", Integer.class).build());
	        }

      return sql;
    }
	 private void processIntercept(Object[] queryArgs) {
	      RowBounds rowBounds = (RowBounds)queryArgs[this.ROWBOUNDS_INDEX];
	      int offset = rowBounds.getOffset();
	      int limit = rowBounds.getLimit();
	      if ((this.dialect.supportsLimit()) && ((offset != 0) || (limit != 2147483647)))
	      {
	        MappedStatement ms = (MappedStatement)queryArgs[this.MAPPED_STATEMENT_INDEX];
	        Object parameter = queryArgs[this.PARAMETER_INDEX];
	        boolean symbolable = this.dialect.supportsLimitOffset();
	        queryArgs[this.MAPPED_STATEMENT_INDEX] = createMappedStatement(ms, symbolable, parameter, offset, limit);

	        if ((symbolable) && (offset > 0)) {
	          offset = 0;
	        }
	        limit = 2147483647;
	        queryArgs[this.ROWBOUNDS_INDEX] = new RowBounds(offset, limit);
	      }
	    }
	 private MappedStatement createMappedStatement(MappedStatement ms, boolean symbolable, Object parameter, int offset, int limit)
	    {
	      BoundSql boundSql = ms.getBoundSql(parameter);
	      String sql = boundSql.getSql().trim();
	      //处理排序
	      if(parameter != null && parameter instanceof Map)// sort=LAST_LOGIN_TIME, order=asc, 
          {
        	  Map<String,Object> map = (Map<String, Object>) parameter;
        	  if(map.get("sort") != null && map.get("order") != null)
        	  {
        		  sql = sql+" order by " + map.get("sort") +" "+ map.get("order");
        	  }
          }
	      
	      List list = boundSql.getParameterMappings();
	      if (list.size() == 0) {
	        list = new ArrayList();
	      }
	      if (symbolable)
	        sql = this.dialect.getLimitString(sql, offset, limit);
	      else {
	        sql = this.dialect.getLimitString(sql, 0, limit);
	      }
	      sql = addParameter(list, ms, parameter, offset, limit, sql, symbolable);
          
	      BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, list, boundSql.getParameterObject());

	      return copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
	    }

	 public void setDialect(Dialect dialect) {
	      this.dialect = dialect;
	 }


	 private class BoundSqlSqlSource implements SqlSource {
	      private BoundSql boundSql;

	      public BoundSqlSqlSource(BoundSql boundSql) {
	        this.boundSql = boundSql;
	      }

	      public BoundSql getBoundSql(Object parameterObject) {
	        return this.boundSql;
	      }
	    }
	  }

